/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.loaders.lists;

import java.lang.ref.*;
import java.util.*;
import java.util.concurrent.*;
import ch.trackedbean.common.*;

/**
 * Manager for content lists (used as content provider for ComboBoxes, Lists etc).
 * 
 * @author M. Hautle
 */
public final class ListManager {
	/** Singleton. */
	public static final ListManager ME = new ListManager();

	/** Cache holding loaded, cachable lists. */
	private Map<String, Reference<List<?>>> cache = new ConcurrentHashMap<String, Reference<List<?>>>();

	/** The list of the loaders. */
	private Map<String, IListLoader<?>> loaders = new ConcurrentHashMap<String, IListLoader<?>>();

	/** The loader to use if none is found in {@link #loaders}. */
	private IListLoader<?> defaultLoader;

	/** The {@link IExceptionHandler} of this manager. */
	private IExceptionHandler exceptionHandler = new DefaultExceptionHandler(ListManager.class);

	/**
	 * Default constructor.
	 */
	private ListManager() {
	}

	/**
	 * Tries to load the desired list from the cache (using only the type information).<br>
	 * This is a convenience method for {@link #getList(Class, String, Map)}.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @return The list
	 */
	public <T> List<T> getList(Class<T> type) {
		return getList(type, null, null);
	}

	/**
	 * Tries to load the desired list from the cache (using only the type information).<br>
	 * This is a convenience method for {@link #getList(Class, String, Map)}.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @param subType Additional string specifying the "subtype" to load (may be null)
	 * @return The list
	 */
	public <T> List<T> getList(Class<T> type, String subType) {
		return getList(type, subType, null);
	}

	/**
	 * Tries to load the desired list from the cache (using only the type and subtype information), if it's not in the cache then
	 * {@link #loadList(Class,String, Map)} will be called.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @param subType Additional string specifying the "subtype" to load (may be null)
	 * @param settings Additional settings for the load (may be null)
	 * @return A copy of the list
	 */
	@SuppressWarnings("unchecked")
	public <T> List<T> getList(Class<T> type, String subType, Map<Object, Object> settings) {
		final Reference<List<?>> ref = cache.get(getKey(type, subType));
		final List<T> res = ref != null ? (List<T>) ref.get() : null;
		return res != null ? new ArrayList(res) : loadList(type, subType, settings);
	}

	/**
	 * Creates a String key using the given information
	 * 
	 * @param type The content type of the list
	 * @param subType Additional string specifying the "subtype" to load (may be null)
	 * @return The key
	 */
	private String getKey(Class<?> type, String subType) {
		String key = type.getName();
		if (subType != null)
			key += "_" + subType;
		return key;
	}

	/**
	 * Loads the specified list using the registred loader.<br>
	 * The loaded list will be cached (if allowed by the loader).<br>
	 * Usualy you should use {@link #getList(Class,String, Map)} instead of this method.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @param subType Additional string specifying the "subtype" to load (may be null)
	 * @param settings Additional settings for the load (may be null)
	 * @return A copy of the loaded list
	 */
	@SuppressWarnings("unchecked")
	public <T> List<T> loadList(Class<T> type, String subType, Map<Object, Object> settings) {
		try {
			final String key = getKey(type, subType);
			final IListLoader<T> l = (IListLoader<T>) getLoader(key);
			final List<T> res = l.load(type, subType, settings);
			if (res.size() > 0 && l.cacheResult(type, subType, settings))
				cache.put(key, new SoftReference<List<?>>(new ArrayList(res)));
			return res;
		} catch (Exception e) {
			exceptionHandler.exceptionOccourd(new ListLoadingException(type, subType, settings, e));
			return Collections.emptyList();
		}
	}

	/**
	 * Removes the specified list from the cache
	 * 
	 * @param type The content type of the list
	 */
	public void resetList(Class<?> type) {
		resetList(type, null);
	}

	/**
	 * Removes the specified list from the cache
	 * 
	 * @param type The content type of the list
	 * @param subType Additional string specifying the "subtype" to load (may be null)
	 */
	public void resetList(Class<?> type, String subType) {
		cache.remove(getKey(type, subType));
	}

	/**
	 * Clears the complete cache.
	 */
	public void clearCache() {
		cache.clear();
	}

	/**
	 * Returns the loader for the given key.<br>
	 * If no loader is registred the {@link #defaultLoader} will be returned.
	 * 
	 * @param key The internal key of the loader (built by {@link #getKey(Class, String)})
	 * @return The corresponding loader
	 * @throws UnknownLoader If no loader is registred for the given key and no {@link #defaultLoader} is defined
	 */
	private IListLoader<?> getLoader(String key) throws UnknownLoader {
		final IListLoader<?> l = loaders.get(key);
		if (l != null)
			return l;
		if (defaultLoader == null)
			throw new UnknownLoader("No loader specified for " + key + "!");
		return defaultLoader;
	}

	/**
	 * Returns the loader for the given type.<br>
	 * If no loader is registred the {@link #defaultLoader} will be returned.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @param subType Additional string specifying the "subtype" to load (may be null)
	 * @return The corresponding loader
	 * @throws UnknownLoader If no loader is registred for the given key and no {@link #defaultLoader} is defined
	 */
	@SuppressWarnings("unchecked")
	public <T> IListLoader<T> getLoader(Class<T> type, String subType) throws UnknownLoader {
		return (IListLoader<T>) getLoader(getKey(type, subType));
	}

	/**
	 * Use this method to set a loader which is used for all loads which have no "own" loader.<br>
	 * Use {@link #registerLoader(Class,String, IListLoader)} to register "own" loaders.
	 * 
	 * @param defaultLoader The defaultLoader to set.
	 */
	public void setDefaultLoader(IListLoader<?> defaultLoader) {
		this.defaultLoader = defaultLoader;
	}

	/**
	 * Registers a list loader for the given type and subtype.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @param subType Additional string specifying the "subtype" to load (may be null)
	 * @param loader The loader for the given key
	 * @return The previously registred loader or null
	 */
	public <T> IListLoader registerLoader(Class<T> type, String subType, IListLoader<T> loader) {
		return loaders.put(getKey(type, subType), loader);
	}

	/**
	 * Registers a list loader for the given type.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @param loader The loader for the given key
	 * @return The previously registred loader or null
	 */
	public <T> IListLoader registerLoader(Class<T> type, IListLoader<T> loader) {
		return loaders.put(getKey(type, null), loader);
	}

	/**
	 * Removes the list loader for the given type and subtype.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @param subType Additional string specifying the "subtype" to load (may be null)
	 * @return The removed loader or null
	 */
	public <T> IListLoader unregisterLoader(Class<T> type, String subType) {
		return loaders.remove(getKey(type, subType));
	}

	/**
	 * Removes the list loader for the given type.
	 * 
	 * @param <T> The content type of the list
	 * @param type The content type of the list
	 * @return The removed loader or null
	 */
	public <T> IListLoader registerLoader(Class<T> type) {
		return loaders.remove(getKey(type, null));
	}

	/**
	 * Sets the {@link IExceptionHandler} of the manager.
	 * 
	 * @param handler The handler
	 */
	public void setExceptionHandler(IExceptionHandler handler) {
		if (handler == null)
			throw new IllegalArgumentException("Null is not allowed!");
		this.exceptionHandler = handler;
	}
}
